#include "pch.hpp"
#include "Text.hpp"
#include "Font.hpp"

using namespace Engine;
using namespace Engine::Ui;




Text::Text(const QString& text, Widget::weak_ptr parent)
	: Widget(parent)

	, m_text(text)
	, m_fontColor(0, 1, 0, 1)

	, m_horizontalAlignment(HAlignment::Left)
	, m_verticalAlignment(VAlignment::Top)
	, m_verticalFlow(VFlow::TopToBottom)
	, m_horizontalFlow(HFlow::LeftToRight)
{
	// Whenever the widget's state changes, we must query all font properties
	// again, but we don't need to invalidate the cache (the properties might not
	// have changed... this is checked inside onStateChanged).
	stateChanged.connect(boost::bind(&Text::onStateChanged, this, _1));

	// Moving or resizing the widget must invalidate the cache because all
	// glyphs are stored in screen coordinates
	moved.connect(boost::bind(&Cache<Quads>::invalidate, &m_cache));
	resized.connect(boost::bind(&Cache<Quads>::invalidate, &m_cache));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::post_create(ptr _this)
{
	__super::post_create(_this);


	parent()->moved.connect(notify::slot_type(&Cache<Quads>::invalidate, &m_cache)
		.track(weak_from_this())
	);

	parent()->resized.connect(notify::slot_type(&Cache<Quads>::invalidate, &m_cache)
		.track(weak_from_this())
	);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

Text::~Text()
{}
//////////////////////////////////////////////////////////////////////////////////////////////////




QString Text::text() const
{
	return m_text;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::setText(const QString& text, bool force)
{
	// We don't need to bother calculating the new
	// size if the value hasn't really changed:
	// The calculation might take some time if the
	// text is really big
	if(force || m_text != text)
	{
		m_text = text;
		auto size = calculateSize();
		setMinimumSize(size);
		setClientSize(size);
		m_cache.invalidate();
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::appendText(const QString& text)
{
	setText(m_text + text);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::appendText(QChar c)
{
	setText(m_text + c);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::prependText(const QString& text)
{
	setText(text + m_text);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::prependText(QChar c)
{
	setText(c + m_text);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




const FontDesc& Text::fontDesc() const
{
	return m_fontDesc;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

HAlignment::Type Text::horizontalAlignment() const
{
	return m_horizontalAlignment;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::setHorizontalAlignment(HAlignment::Type alignment)
{
	m_horizontalAlignment = alignment;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

VAlignment::Type Text::verticalAlignment() const
{
	return m_verticalAlignment;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::setVerticalAlignment(VAlignment::Type alignment)
{
	m_verticalAlignment = alignment;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

VFlow::Type Text::verticalFlow() const
{
	return m_verticalFlow;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::setVerticalFlow(VFlow::Type verticalFlow)
{
	m_verticalFlow = verticalFlow;
}
///////////////////////////////////////////////////////////////////////////////////////////////////




Widgets::Type Text::type() const
{
	return Widgets::Text;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::onStateChanged(Widget::weak_ptr that)
{
	if(that.expired())
		return;

	auto tmp = that.lock();

	if(this == tmp.get())
	{
		auto widgetStyle = style();
		auto oldFontDesc = m_fontDesc;
		auto oldFontColor = m_fontColor;

		m_fontDesc  = fontDesc(widgetStyle, state());
		m_fontColor = widgetStyle->attribute<math::color>(Attributes::FontColor, state(), m_fontColor);

		auto size = calculateSize();
		setMinimumSize(size);
		setClientSize(size);

		// In case anything about the font description has been changed,
		// we have to set the text again, because the widget must very likely
		// be resized
		if(oldFontDesc != m_fontDesc || oldFontColor != m_fontColor)
		{
			m_cache.invalidate();
		}
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::update(NestedState& state)
{
	Widget::update(state);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

FontDesc Text::fontDesc(const WidgetStyle::ptr& style, WidgetState::Type state)
{
	FontDesc desc;
	desc.name    = style->attribute<QString>(Attributes::Font, state);
	desc.size    = boost::numeric_cast<uint8>(style->attribute<std::size_t>(Attributes::FontSize, state));
	desc.outline = boost::numeric_cast<uint8>(style->attribute<std::size_t>(Attributes::FontOutline, state, 0));
	desc.bold    = style->attribute<bool>(Attributes::FontWeight, state, false);
	desc.italic  = style->attribute<bool>(Attributes::FontStyle, state, false);

	return desc;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void Text::renderAdditional(Quads& quads, const NestedState& state)
{
	//
	// In case the cache is invalid, we have to render
	// the text again.
	//
	if(m_cache.isInvalid())
	{
		//
		// Calculate the new client size & minimum size
		//
		auto size = calculateSize();
		setMinimumSize(size);
		setClientSize(size);

		//
		// Extract font properties and calculate font's offset
		// relative to the widget (taking into account properties
		// like vertical & horizontal alignment).
		//
		auto font = styleDefinition()->font(m_fontDesc);
		int2 p = state.parentPos() + pos();
		int height = font.calculateHeight(m_text);

		switch(m_horizontalAlignment)
		{
		case HAlignment::Center:
			// When using centered alignment, the renderer
			// centers the given text around the given position
			// however we want the text being centered over the size
			// hence the position is simply shifted to the center of
			// the widget
			p.x += size.width / 2;
			break;
		case HAlignment::Right:
			// When using right alignment, the renderer
			// aligns the text on the right-hand side of the given position, but
			// we want the text being aligned towards the right side of the
			// widget, hence the position is simply shifted to the right side
			// of the widget
			p.x += size.width;
			break;
		}

		if(m_verticalFlow == VFlow::BottomToTop)
		{
			// When using the bottom to top flow, the renderer
			// aligns the text to the upper side of the position position
			switch(m_verticalAlignment)
			{
			case VAlignment::Top:
				p.y += height;
				break;
			case VAlignment::Center:
				// The text should be centered => simple calculation
				p.y += (height + size.height) / 2;
				break;
			case VAlignment::Bottom:
				// We want the text being aligned towards the bottom of the
				// widget.
				p.y += size.height;
				break;
			}
		}
		else
		{
			switch(m_verticalAlignment)
			{
			case VAlignment::Center:
				p.y += (size.height - height) / 2;
				break;
			case VAlignment::Bottom:
				p.y += size.height - height;
				break;
			}
		}

		m_cache.validate();
		m_cache->clear();
		font.render(m_cache.get(), p, m_fontColor, m_text, m_horizontalAlignment, m_verticalFlow, m_horizontalFlow);
	}

	quads.insert(quads.end(), m_cache->begin(), m_cache->end());
}
///////////////////////////////////////////////////////////////////////////////////////////////////

uint32 Text::numAdditionalQuads() const
{
	return BasicFont::count(m_text);
}
///////////////////////////////////////////////////////////////////////////////////////////////////




intsize2 Text::calculateSize() const
{
	// The vertical size is fixed, but the horizontal size may grow/shrink
	auto font = styleDefinition()->font(fontDesc());
	auto size = font.calculateSize(m_text, m_horizontalAlignment);
	if(m_horizontalAlignment == HAlignment::Center)
		size.width *= 2;

	return size;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
